package org.ykh.service.policy.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import org.ykh.common.utils.HttpUtils;
import org.ykh.service.exception.BusinessException;
import org.ykh.service.policy.annotation.Shield;
import org.ykh.service.policy.RestrictPolicy;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

/**
 * 去重，防抖
 *
 * @author ykh
 */

@Aspect
@Component
@Slf4j
public class RequestNodup implements ApplicationContextAware {

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 防抖
     * TODO:未来需要与认证系统结合
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object nodup(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        String encoded = HttpUtils.requestHashing(request, "MD5");

        final String ip = HttpUtils.ip(request);
        String key = ip + ":" + encoded;

        final ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        final Boolean value = opsForValue.setIfAbsent(key, "1", Duration.ofSeconds(5));
        if (value != null && !value) {
            //加一并重置过期时间
            opsForValue.increment(key);
            redisTemplate.expire(key,Duration.ofSeconds(5));
            throw new BusinessException("2000", "请求过于频繁！！！", HttpStatus.TOO_MANY_REQUESTS);
        }
        return proceedingJoinPoint.proceed();
    }

    /**
     * 屏蔽ip
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(org.ykh.service.policy.annotation.Shield)")
    public Object shieldIP(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

        final String ip = HttpUtils.ip(request);
        String encoded = HttpUtils.requestHashing(request, "MD5");

        final MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        final Method method = signature.getMethod();
        final Shield shield = method.getAnnotation(Shield.class);

        final RestrictPolicy restrictPolicy = applicationContext.getBean(shield.policy());

        final ChronoUnit chronoUnit = ChronoUnit.valueOf(shield.intervalTimeUnit());
        final Duration of = Duration.of(shield.interval(), chronoUnit);
        final int threshold = shield.threshold();
        if (restrictPolicy.isRestrict(request,encoded, of,threshold)){
            throw new BusinessException("200008", "拒绝提供服务");
        }

        restrictPolicy.restrictPolicy(request,encoded,of,threshold);

        return proceedingJoinPoint.proceed();
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
